'''
Created on Oct 3, 2010

@author: morgan
'''
'''
KML Feature classes

Created on Sep 28, 2010

@author: morgan
'''

from __xml__ import createElement, createComment
from __string__ import T
 
from base import KMLObject
from data import Snippet, Link
from enum import refreshVisibility, flyToView
from geom import LinearRing, Point, LineString, MultiGeometry 
from styles import Style
from time import TimeStamp, TimeSpan
from views import LookAt, Camera

__all__ = ['Feature', 'Container', 'Document', 'Folder', 'Placemark', 
           'NetworkLink']

class Feature(KMLObject):
    def __init__(self, id="", name = "", description = "", snippet = None, 
                 visibility = True, open = False, styles = [], 
                 timePrimitive= None, comments = [], styleUrl = "", 
                 view = None, address = "" ):
        KMLObject.__init__(self, id)
        self.name = name
        if address:
            self.address = address
        self.description = description
        if snippet:
            self.Snippet = snippet
        self.visibility = visibility
        self.open = open
        self.Styles = styles
        self.comments = comments
        self.styleUrl = styleUrl
        if timePrimitive:
            self.timePrimitive = timePrimitive
        if view:
            self.View = view
        self.address = address
    
    @property
    def supportedFeatures(self):
        return __all__[2:]

    @property
    def View(self):
        """
        An instance of a descendant of kml.View that contains the view or None.
        If view is None, no view will be included in the resulting XML.
        (read/write)
        """
        if hasattr(self, "_view"):
            return self._view
        else:
            return None
        
    @View.setter
    def View(self, view):
        if not any(isinstance(view, aType) for aType in [LookAt, Camera]):
            raise TypeError("Expecting view to be LookAt or Camera.")
        if view is None:
            del self._view
        self._view = view
        
    @property
    def timePrimitive(self):
        """
        An instance of a descendant of kml.TimePrimitive, currently either a
        kml.TimeSpan or kml.TimeStamp, or None.  If timePrimitive is None, no
        time information will be included in the resulting XML.
        """
        if hasattr(self, "_timePrimitive"):
            return self._timePrimitive
        else:
            return None
    
    @timePrimitive.setter
    def timePrimitive(self, value):
        if value is None and hasattr(self, "_timePrimitive"):
            return self._timePrimitive
        else:
            return None

    @property
    def name(self):
        """ A string representing the name of the Feature. If empty, the 
        <name> element of the Feature will be set to 'Unknown'. (read/write) """
        return self._name
    
    @name.setter
    def name(self, value):
        if type(value) != str:
            raise TypeError("Expecting name to be a string.")
        else:
            self._name = value
    
    @property
    def description(self):
        """
        A string representing the description. HTML allowed. If empty, 
        the <description> element will be set to 'None'. (read/wrte) 
        """
        return self._description
    
    @description.setter
    def description(self, value):
        if not value:
            self._description = ""
        elif type(value) == str:
            self._description = value
        else:
            raise TypeError("description must be a string")        

    @property
    def address(self):
        """
         A string that contains the street address of the Feature. If empty,
         no <address> element will be included the resulting XML. (read/write)  
         """
        return self._address
    
    @address.setter
    def address(self, value):
        if type(value) == str:
            self._address = value
        else:
            raise TypeError("Address must be a string.")

    @property
    def comments(self):
        """ A list of strings containing the commnets """
        return self._comments

    @comments.setter
    def comments(self, value):
        if type(value) == str:
            self._comments = [value]
        elif type(value) == list:
            self._comments = value
        else:
            self._comments = [str(value)]
        
    def addComment(self, comment):
        if not type(comment) == str:
            raise TypeError("Expecting comment to be a string.")
        else:
            self._comments.append(comment)
    
    def removeComment(self, commentOrIndex):
        if type(commentOrIndex) == int or type(commentOrIndex) == str:
            self._comments.remove(commentOrIndex)
        else:
            raise TypeError("Expecting comment to be a string or index.")            
   
    def addStyle(self, style):
        """ Adds a Style to the Feature. """
        if not isinstance(style, Style):
            raise TypeError("Expecting style to be a Style instance")
        else:
            self._styles.append(style)
            
    def removeStyle(self, StyleOrIndex):
        """ Removes a Style from the Feature. I{StyleOrIndex} is a Style or 
        and the 0-based index of the Style in B{Feature.Styles}. """
        if isinstance(StyleOrIndex, Style) or type(StyleOrIndex) == int:
            self._styles.remove(StyleOrIndex)
        else:
            raise TypeError("Expecting style to be a Style instance")
    
    @property
    def Styles(self):
        """ A list or tuple of kml.Style instances or None """
        if hasattr(self, "_styles"):
            return self._styles
        else:
            return None
    
    @Styles.setter
    def Styles(self, value):
        if value is None and hasattr(self, "_styles"):
            del self._styles
        elif not type(value) in [list, tuple]:
            raise TypeError("Expecting styles to be a list or tuple "+
                            "of Style instances")
        else:
            styles = []
            for style in styles:
                if not isinstance(style, Style):
                    raise TypeError("Expecting styles to be a list "+
                                    "or tuple of Style instances")
                else:
                    styles.append(style)
            self._styles = styles
    
    @Styles.deleter
    def Styles(self):
        del self._styles
    
    @property
    def styleUrl(self):
        if hasattr(self, "_styleurl"):
            return self._styleUrl
        else:
            return None

    @styleUrl.setter
    def styleUrl(self, url):
        """
        Eventually we may do more than type checking here.  For example, we
        might check to see if a "#"-prefixed URL really exists in the document,
        or we might check to see if url is formed correctly.
        """ 
        if url is None and hasattr(self, "_styleUrl"):
            del self._styleUrl
        elif not type(url) == str:
            raise TypeError("Expecting styleUrl to be a string")
        else:
            self._styleUrl = url
            
    @styleUrl.deleter
    def styleUrl(self):
        del self._styleUrl
            
    def clearStyleUrl(self):
        self.styleUrl = None  
    
    @property
    def Snippet(self):
        if hasattr(self, "_snippet"):
            return self._snippet
        else:
            return None    
    
    @Snippet.setter
    def Snippet(self, snippet):
        if type(snippet) is Snippet:
            self._snippet = snippet
        elif snippet is None:
            del self._snippet
        else:
            raise TypeError("Expecting snippet to be a Snippet instance")       
    
    @property
    def visibility(self):
        return self._visibility
    
    @visibility.setter
    def visibility(self, visibility):
        if type(visibility) != bool:
            raise ValueError("Feature was expecting visibility to be a boolean value")
        else:
            self._visibility = visibility
    
    def setVisible(self):
        self.visibility = True
    
    def setHidden(self):
        self.visibility = False
        
    def toggleVisibility(self):
        if self.visibility:
            self.setHidden()
        else:
            self.setVisible()
    @property
    def open(self):
        return self._open
    
    @open.setter
    def open(self, value):
        if type(value) != bool:
            raise TypeError("Expecting open to be a boolean value.")
        else:
            self._open = value
            
    def toXmlNode(self,kml):
        docNode = KMLObject.toXmlNode(self, kml)
        if self.comments:
            for comment in self.comments:
                docNode.appendChild(createComment(kml._doc, comment))
        if self.name:
            docNode.appendChild(createElement(kml._doc,"name", self.name))

        if not self.visibility:
            docNode.appendChild(createElement(kml._doc, "visibility", 
                                                    self.visibility))
        if self.open:
            docNode.appendChild(createElement(kml._doc, "open", self.open))
        if self.address:
            docNode.appendChild(createElement(kml._doc, "address", self.address))
        if self.Snippet:
            docNode.appendChild(self._snippet.toXmlNode(kml))
    
        if self.description:
            docNode.appendChild(createElement(kml._doc, "description", 
                                                   self.description))
        if self.View:
            docNode.appendChild(self.View.toXmlNode(kml))
        if self.timePrimitive:
            docNode.appendChild(self.timePrimitive.toXmlNode(kml))
        if self.styleUrl:
            docNode.appendChild(createElement(kml._doc, "styleUrl", 
                                               self.styleUrl))
        if self.Styles:
            for style in self.Styles:
                docNode.appendChild(style.toXmlNode(kml))
        return docNode
        
    def parseKMLNode(self, KMLNode):
        KMLObject.parseKMLNode(self, KMLNode)
        if KMLNode.tagName not in self.supportedFeatures:
            msg = T("Unsupported Feature tag $tag")
            raise TypeError(msg.substitute(tag=KMLNode.tagName))  
        elems = KMLNode.getElementsByTagName("name")
        if elems and elems[0].parentNode == KMLNode:
            self.name = str(elems[0].childNodes[0].data)
        elems = KMLNode.getElementsByTagName("visibility")
        if elems and elems[0].parentNode == KMLNode:
            self.visibility = int(elems[0].childNodes[0].data) != 0
        elems = KMLNode.getElementsByTagName("open")
        if elems and elems[0].parentNode == KMLNode:
            self.open = int(elems[0].childNodes[0].data) != 0      
        elems = KMLNode.getElementsByTagName("address")
        if elems and elems[0].parentNode == KMLNode:
            self.address = elems[0].childNodes[0].data.strip()
        elems = KMLNode.getElementsByTagName("Snippet")
        if elems and elems[0].parentNode == KMLNode:
            self.Snippet = Snippet()
            self.Snippet.parseKMLNode(elems[0])
        elems = KMLNode.getElementsByTagName("description")
        if elems and elems[0].parentNode == KMLNode:
            self.description = str(elems[0].childNodes[0].data)
        elems = KMLNode.getElementsByTagName("LookAt")
        if elems and elems[0].parentNode == KMLNode:
            self.View = LookAt()
            self.View.parseKMLNode(elems[0])
        elems = KMLNode.getElementsByTagName("Camera")
        if elems and elems[0].parentNode == KMLNode:
            self.View = Camera()
            self.View.parseKMLNode(elems[0])
        elems = KMLNode.getElementsByTagName("TimeStamp")
        if elems and elems[0].parentNode == KMLNode:
            self.timePrimitive = TimeStamp()
            self.timePrimitive.parseKMLNode(elems[0])
        elems = KMLNode.getElementsByTagName("TimeSpan")
        if elems and elems[0].parentNode == KMLNode:
            self.timePrimitive = TimeSpan()
            self.timePrimitive.parseKMLNode(elems[0])
        elems = KMLNode.getElementsByTagName("styleUrl")
        if elems and elems[0] == KMLNode:
            self.styleUrl = elems[0].childNodes[0].data
        elems = KMLNode.getElementsByTagName("Style")
        if elems:
            for elem in elems:
                if elem.parentNode == KMLNode:
                    style = Style()
                    style.parseKMLNode(elem)
                    self.addStyle(style)
            
class Container(Feature):
    def __init__(self, id="", features=[], **kwds):
        self.children = features
        Feature.__init__(self, id=id, **kwds)
        
    def addChildFeature(self, feature):
        if not issubclass(feature.__class__, Feature):
            raise TypeError("Feature was expecting feature to be an instance "+
                            "of a subclass of kml.Feature")
        else:
            self._features.append(feature)
            
    def removeChildFeature(self, featureOrIndex):
        if issubclass(featureOrIndex.__class__, Feature):
            self._features.remove(featureOrIndex)
        elif type(featureOrIndex) == int:
            self._features.remove(featureOrIndex)
        else:
            raise TypeError("Feature was expecting featureOrIndex to be an instance "+
                            "of a subclass of kml.Feature or an integral index.")
    def clearChildFeatures(self):
        self._features = []
        
    @property
    def children(self):
        if hasattr(self, "_features"):
            return self._features
        else:
            return None

    @children.setter
    def children(self, children):
        if children is None:
            if hasattr(self, "_features"):
                return self._features
        elif type(children) in [list, tuple]:
            features = []
            for child in children:
                if issubclass(child, Feature):
                    features.append(child)
                else:
                    raise TypeError("Children must be a list or tuple of Features")
            self._features = features
        else:
            raise TypeError("Children must be a list or tuple of Features")

    @children.deleter
    def children(self):
        del self._features
                    
    def toXmlNode(self, kml):
        docNode = Feature.toXmlNode(self, kml)
        if self._features:
            for feature in self._features:
                docNode.appendChild(feature.toXmlNode(kml))
        return docNode
    
    def parseKMLNode(self, KMLNode):
        Feature.parseKMLNode(self, KMLNode)
        for tag in self.supportedFeatures:
            try:
                nodes = []
                nodes = KMLNode.getElementsByTagName(tag)
            except:
                pass
            else:
                for node in nodes:
                    if tag == 'Document':
                        feature = Document()
                        feature.parseKMLNode(node)
                        self.addChildFeature(feature)  
                    elif tag == 'Folder':
                        feature = Folder()
                        feature.parseKMLNode(node)
                        self.addChildFeature(feature)                          
                    elif tag == 'Placemark':
                        feature = Placemark()
                        feature.parseKMLNode(node)
                        self.addChildFeature(feature)  
                    else:
                        msg=T("Unsupported Feature element $elem")
                        raise(msg.substitute(elem=tag))
               

class Document(Container):
    def __init__(self, id="", *args, **kwds):
        self.__tagName__ = "Document"
        Container.__init__(self, id, *args, **kwds)

class Folder(Container):
    def __init__(self, id="", *args, **kwds):
        self.__tagName__ = "Folder"
        Container.__init__(self, id, *args, **kwds)
              
class Placemark(Feature):
    def __init__(self, id="", geometry = None, *args, **kwds):
        self.__tagName__ = "Placemark"
        Feature.__init__(self, id, *args, **kwds)
        if geometry:
            self.Geometry = geometry
        else:
            self.clearGeometry()

    def _getSupportedGeometryTypes(self):
        return [Point, LineString, LinearRing, MultiGeometry]
    
    supportedGeometryTypes = property(_getSupportedGeometryTypes)
    
    @property
    def Geometry(self):
        return self._geometry
    
    @Geometry.setter
    def Geometry(self, theGeometry):
        if not any(type(theGeometry) == aType for aType in self.supportedGeometryTypes):
            raise TypeError("Expecting geometry to be "+
                            "Point, LineString, MultiGeometry, or LinearRing")
        self._geometry = theGeometry
 
    def clearGeometry(self):
        self._geometry = None
        
    def toXmlNode(self, kml):
        docNode = Feature.toXmlNode(self, kml)
        if self.Geometry:
            docNode.appendChild(self.Geometry.toXmlNode(kml))
        return docNode
        
    def parseKMLNode(self, KMLNode):
        Feature.parseKMLNode(self, KMLNode)
        if KMLNode.getElementsByTagName("Point") != []:
            self._geometry = Point()
            self._geometry.parseKMLNode(KMLNode.getElementsByTagName("Point")[0])
        elif KMLNode.getElementsByTagName("LineString") != []:
            self._geometry = LineString()
            self._geometry.parseKMLNode(KMLNode.getElementsByTagName("LineString")[0])
        elif KMLNode.getElementsByTagName("LinearRing") != []:
            self._geometry = LinearRing()
            self._geometry.parseKMLNode(KMLNode.getElementsByTagName("LinearRing")[0])
        elif KMLNode.getElementsByTagName("MultiGeometry") != []:
            self._geometry = MultiGeometry()
            self._geometry.parseKMLNode(KMLNode.getElementsByTagName("MultiGeometry")[0])

class NetworkLink(Feature):
    def __init__(self, id="", refreshVisibility=False, flyToView=False, 
                 Link=None, *args, **kwds):
        self.__tagName__ = "NetworkLink"
        Feature.__init__(self, id, *args, **kwds)
        self.refreshVisibility = refreshVisibility
        self.flyToView = flyToView 
    
    @property
    def refreshVisibility(self):
        return self._refreshVisibility
    
    @refreshVisibility.setter
    def refreshVisibility(self, value):
        if type(value) == bool:
            self._refreshVisibility = value
        elif type(value) == refreshVisibility:
            self._refreshVisibility = value.value
        else:
            raise TypeError("refreshVisibility must be a boolean or kml.refreshVisibility instance")
    
    @property
    def flyToView(self):
        return self._flyToView
    
    @flyToView.setter
    def flyToView(self, value):
        if type(value) == bool:
            self._flyToView = value
        elif type(value) == flyToView:
            self._flyToView = value.value
        else:
            raise TypeError("flyToView must be a boolean or a kml.flyToView instance")
    
    @property
    def Link(self):
        if hasattr(self, "_link"):
            return self._link
        else:
            return None
        
    @Link.setter
    def Link(self, value):
        if value is None:
            raise ValueError("Link must not be None")
        elif type(value) is Link:
            self._link = value
        elif type(value) is str:
            self._link = Link(href=value)
        else:
            raise TypeError("Link must be a kml.Link instance or str")
        
    def toXmlNode(self, kml):
        node = Feature.toXmlNode(self, kml)
        if self.refreshVisibility:
            rv = refreshVisibility(self.refreshVisibility)
            node.appendChild(rv.toXmlNode(kml))
        if self.flyToView:
            ftv = flyToView(self.flyToView)
            node.appendChild(ftv.toXmlNode(kml))
        if self.Link:
            node.appendChild(self.Link.toXmlNode(kml))
        else:
            raise AttributeError("Must define a Link!")
        return node
    
    def parseKMLNode(self, KMLNode):
        Feature.parseKMLNode(self, KMLNode)
        elems = KMLNode.getElementsByTagName("refreshVisibility")
        if elems:
            rv = refreshVisibility()
            rv.parseKMLNode(elems[0])
            self.refreshVisibility = rv
        elems = KMLNode.getElementsByTagName("flyToView")
        if elems:
            ftv = flyToView()
            ftv.parseKMLNode(elems[0])
            self.flyToView = ftv
        elems = KMLNode.getElementsByTagName("Link")
        if elems:
            link = Link()
            link.parseKMLNode(elems[0])
            self.Link = link
        else:
            raise Exception("<NetworkLink> element does not contain a <Link>!")

    
    
